/*
 * $Id$
 * $HeadURL$
 *
 * ==============================================================================
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.jettyrunner.server;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;

import javax.management.MBeanServer;
import javax.transaction.UserTransaction;

import org.eclipse.jetty.jmx.MBeanContainer;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.NCSARequestLog;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.RolloverFileOutputStream;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.WebAppClassLoader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.xml.XmlConfiguration;

/**
 * @author Andrei Solntsev
 * @version $Revision: $ $Date: $ $Author: $
 */
public class Runner
{
	protected static final String CLASS_VERSION = "$Revision: 1.3.2.1 $ $Author: rprasnakou_epam $";

    public static final String[] __plusConfigurationClasses = new String[] {
    	"org.eclipse.jetty.webapp.WebInfConfiguration",
        "org.eclipse.jetty.webapp.WebXmlConfiguration",
        "org.eclipse.jetty.webapp.MetaInfConfiguration",
        "org.eclipse.jetty.webapp.FragmentConfiguration",
        "org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
        "org.eclipse.jetty.webapp.TagLibConfiguration",
        "org.eclipse.jetty.plus.webapp.Configuration",
        "org.eclipse.jetty.plus.webapp.EnvConfiguration"};

    // private HandlerCollection _contexts;
    private MBeanContainer _mBeanContainer;
    protected Server _server;
	protected URLClassLoader _classLoader;
	protected List<URL> _classpath=new ArrayList<URL>();
	protected ContextHandlerCollection _contexts;
	protected RequestLogHandler _logHandler;
	protected String _logFile;
	protected String _configFile;
	protected UserTransaction _ut;
	protected String _utId;
	protected String _txMgrPropertiesFile;
	protected Random _random = new Random();
	protected boolean _isTxServiceAvailable=false;
	protected boolean _clusteredSessions=true;
	protected boolean _isResourceRootSystem = false;

	private String host="localhost";
	private int port=8080;

	private boolean transactionManagerProcessed = false;
	private boolean runnerServerInitialized = false;

	protected String _instanceName;
	protected List<String> _webapps = new ArrayList<String>(6);
	protected Configuration configuration;

	public Runner()
	{
	}

	/*public Runner(String[] args) throws Exception
	{
		configure(args);
	}*/

	public Runner(String... args) throws Exception
	{
		configure(args);
	}

	public void usage(String error)
	{
	    if (error!=null)
	        System.err.println("ERROR: "+error);
	    System.err.println("Usage: java [-DDEBUG] [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...] ");
	    System.err.println("Server Options:");
	    System.err.println(" --log file                         - request log filename (with optional 'yyyy_mm_dd' wildcard");
	    System.err.println(" --out file                         - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard");
	    System.err.println(" --host host                   		- host to run server on (default localhost)");
	    System.err.println(" --port n                           - port to listen on (default 8080)");
	    System.err.println(" --system_resource_root boolean     - indicator for setting host and port as system property 'RESOURCE_ROOT' (default false)");
	    System.err.println(" --jar file                         - a jar to be added to the classloader");
	    System.err.println("--jdbc classname properties jndiname - classname of XADataSource or driver; properties string; name to register in jndi");
	    System.err.println(" --lib dir                          - a directory of jars to be added to the classloader");
	    System.err.println(" --classes dir                      - a directory of classes to be added to the classloader");
	    System.err.println(" --txFile                           - override properties file for Atomikos");
	    System.err.println(" --config file                      - a jetty xml config file to use instead of command line options");
	    System.err.println(" --instance name                    - name of instance (database) to connect to ");
	    System.err.println(" --webapp webapp1                   - configuration of webapp1 ");
	    System.err.println(" --webapp webappN                   - configuration of webappN ");
	    System.err.println("Context Options:");
	    System.err.println(" --path /path       - context path (default /)");
	    System.err.println(" context            - WAR file, web app dir or context.xml file");
	    System.exit(1);
	}

	public Runner configure(String... args) throws Exception
	{
		Log.getLog().info("Configure JettyServer: " + Utils.arrayAsString(args), args, null);

	    // handle classpath bits first so we can initialize the log mechanism.
	    for (int i=0;i<args.length;i++)
	    {
	        if ("--lib".equals(args[i]))
	        {
	            Resource lib = Resource.newResource(args[++i]);
	            if (!lib.exists() || !lib.isDirectory())
	                usage("No such lib directory "+lib);
	            expandJars(lib);
	        }
	        else if ("--jar".equals(args[i]))
	        {
	            Resource jar = Resource.newResource(args[++i]);
	            if (!jar.exists() || jar.isDirectory())
	                usage("No such jar "+jar);
	            _classpath.add(jar.getURL());
	        }
	        else if ("--classes".equals(args[i]))
	        {
	            Resource classes = Resource.newResource(args[++i]);
	            if (!classes.exists() || !classes.isDirectory())
	                usage("No such classes directory "+classes);
	            _classpath.add(classes.getURL());
	        }
	        else if (args[i].startsWith("--"))
	            i++;
	    }

	    initClassLoader();
	    // Log.initialize();

	    try
	    {
	        if (Thread.currentThread().getContextClassLoader().loadClass("com.atomikos.jdbc.SimpleDataSourceBean")!=null)
	            _isTxServiceAvailable=true;
	    }
	    catch (ClassNotFoundException e)
	    {
	        _isTxServiceAvailable=false;
	    }
	    if (System.getProperties().containsKey("DEBUG"))
	        Log.getLog().setDebugEnabled(true);

	    Log.info("Runner");
	    Log.debug("Runner classpath {}",_classpath);

	    String contextPath="/";
	    boolean contextPathSet=false;

	    for (int i=0;i<args.length;i++)
	    {
	    	if ("--host".equals(args[i]))
	    		host=args[++i];
	    	else if ("--port".equals(args[i]))
	            port=Integer.parseInt(args[++i]);
	        else if ("--log".equals(args[i]))
	            _logFile=args[++i];
	        else if ("--out".equals(args[i]))
	        {
	            String outFile=args[++i];
	            PrintStream out = new PrintStream(new RolloverFileOutputStream(outFile,true,-1));
	            Log.info("Redirecting stderr/stdout to "+outFile);
	            System.setErr(out);
	            System.setOut(out);
	        }
	        else if ("--path".equals(args[i]))
	        {
	            contextPath=args[++i];
	            contextPathSet=true;
	        }
	        else if ("--config".equals(args[i]))
	        {
	            _configFile=args[++i];

	            File file1 = new File(_configFile);
	            File file2 = file1.getCanonicalFile();
                URL url2=file2.toURI().toURL();
	        }
	        else if ("--instance".equals(args[i]))
	        {
	            _instanceName=args[++i];
	        }
	        else if ("--webapp".equals(args[i]))
	        {
	        	_webapps.add( args[++i] );
	        }
	        else if ("--lib".equals(args[i]))
	        {
	            ++i;//skip
	        }
	        else if ("--jar".equals(args[i]))
	        {
	            ++i; //skip
	        }
	        else if ("--classes".equals(args[i]))
	        {
	            ++i;//skip
	        }
	        else if ("--txFile".equals(args[i]))
	        {
	            _txMgrPropertiesFile=args[++i];
	        }
	        else if ("--jdbc".equals(args[i]))
	        {
	            i=configJDBC(args,i);
	        }
	        else if ("--system_resource_root".equals(args[i]))
	        {
	            _isResourceRootSystem = Boolean.parseBoolean(args[++i]);
	        }
	        else if ("".equals(args[i]))
	        {
	        	// ignore
	        }
	        else // process contexts
	        {
	        	runServer(); // FIXME I didn't test this branch

	            // Create a context
	            Resource ctx = Resource.newResource(args[i]);
	            if (!ctx.exists())
	                usage("Context '"+ctx+"' does not exist");

	            // Configure the context
	            if (!ctx.isDirectory() && ctx.toString().toLowerCase().endsWith(".xml"))
	            {
	                // It is a context config file
	                XmlConfiguration xmlConfiguration=new XmlConfiguration(ctx.getURL());
	                HashMap<String,Object> properties = new HashMap<String,Object>();
	                properties.put("Server", _server);
	                xmlConfiguration.setProperties(properties);
	                ContextHandler handler=(ContextHandler)xmlConfiguration.configure();
	                _contexts.addHandler(handler);
	                if (contextPathSet)
	                    handler.setContextPath(contextPath);
	            }
	            else
	            {
	                // assume it is a WAR file
	                if (contextPathSet && !(contextPath.startsWith("/")))
	                    contextPath = "/"+contextPath;

	                addWebapp(ctx.toString(), contextPath);
	            }
	        }
	    }

	    runServer();

	    configure();

	    if (_server==null)
	        usage("No Contexts defined");
	    _server.setStopAtShutdown(true);
	    _server.setSendServerVersion(true);

	    if (_logFile!=null)
	    {
	        NCSARequestLog requestLog = new NCSARequestLog(_logFile);
	        requestLog.setExtended(false);
	        _logHandler.setRequestLog(requestLog);
	    }

	    return this;
	}

	private void runServer() throws Exception
	{
	    if ( !transactionManagerProcessed ) // to be executed once upon starting to process contexts
	    {
	        processTransactionManagement();
	        transactionManagerProcessed = true;
	    }

	    if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
	    {
	        if (_server == null) // server not initialized yet
	        {
	            // build the server
	            _server = new Server();

	        }

	        //apply a config file if there is one
	        if (_configFile != null)
	        {
	            XmlConfiguration xmlConfiguration = new XmlConfiguration(Resource.newResource(_configFile).getURL());
	            xmlConfiguration.configure(_server);
	        }

	        //check that everything got configured, and if not, make the handlers
	        HandlerCollection handlers = (HandlerCollection) _server.getChildHandlerByClass(HandlerCollection.class);
	        if (handlers == null)
	        {
		        _contexts = (ContextHandlerCollection) _server.getChildHandlerByClass(ContextHandlerCollection.class);
		        if (_contexts == null)
		        {
		            _contexts = new ContextHandlerCollection();
		        }

	            handlers = new HandlerCollection();

	            handlers.addHandler(_contexts);

	            if (_server.getChildHandlerByClass(DefaultHandler.class) == null)
		        	handlers.addHandler(new DefaultHandler());

	            // check if log handler has been added
		        _logHandler = (RequestLogHandler)_server.getChildHandlerByClass( RequestLogHandler.class );
		        if ( _logHandler == null )
		        {
		            _logHandler = new RequestLogHandler();
		            handlers.addHandler( _logHandler );
		        }

		        _server.setHandler(handlers);
	        }

	        // _contexts = handlers;

	        //check a connector is configured to listen on
	        Connector[] connectors = _server.getConnectors();
	        if (connectors == null || connectors.length == 0)
	        {
	            Connector connector = new SelectChannelConnector();
	            connector.setHost(host);
	            connector.setPort(port);
	            _server.addConnector(connector);
	        }

	        runnerServerInitialized = true;

	        MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
	        _mBeanContainer = new MBeanContainer(mBeanServer);
	        _server.getContainer().addEventListener(_mBeanContainer);
	    }
	}

	private void deployBuiltInApplications() throws MalformedURLException, IOException
	{
		// Those applications will be appended by Eclipse plugin
		// installBuiltInWar("/", "htdocs.war");
		// installBuiltInWar("/pls", "pls.war");
		// installBuiltInWar("/pls2", "pls.war");
		// installBuiltInWar("/god_mode", "god_mode.war");
	}

	private void installBuiltInWar(String context, String warName) throws MalformedURLException, IOException
	{
		if (hasContext(context))
		{
			Log.info("Context " + context + " is already deployed. Skipping built-in " + warName + " ...");
		}
		else
		{
			Log.info("Deploying built-in " + warName + " as context " + context + " ...");
			String warPath = extractBuiltInWar(warName);
			Resource ctx = Resource.newResource(warPath);
			addWebapp(ctx.toString(), context);
		}
	}

	private WebAppContext addWebapp(String webApp, String contextPath)
	{
		String contextName = contextPath;
		if (contextName.equals("ROOT") || contextName.equals("ROOT.war"))
			contextName = "/";

		if (!contextName.startsWith("/"))
			contextName = "/" + contextName;

		WebAppContext webapp = new WebAppContext(_contexts, webApp, contextName);
        // webapp.setConfigurationClasses(__plusConfigurationClasses);// I am not sure if it's needed
		// webapp.setDisplayName(contextName);
		webapp.setParentLoaderPriority(false); // it should be default value (?)

        // _contexts.addHandler(webapp);

		return webapp;
	}

	private String extractBuiltInWar(String warName) throws IOException
	{
		File deploymentFolder = new File("jetty-temp-deploy");
		deploymentFolder.mkdirs();
		// Object temp = deploymentFolder.getAbsolutePath();
		File warFile = new File(deploymentFolder, warName);
		if (warFile.exists())
			return warFile.toString();

		warFile.createNewFile();

		URL warUrl = Thread.currentThread().getContextClassLoader().getResource("webapps/"+warName);
		/*String t1 = warUrl.getPath();
		String t2 = warUrl.getFile();
		JarFile jar = new JarFile(warUrl.toString());
		ZipEntry entry = jar.getEntry(warUrl.toString());
		InputStream in = new BufferedInputStream(jar.getInputStream(entry));*/
		InputStream in = warUrl.openStream();

		OutputStream out = new BufferedOutputStream(new FileOutputStream(warFile));
		try
		{
			byte[] buffer = new byte[2048];
			for (;;)
			{
				int nBytes = in.read(buffer);
				if (nBytes < 0) break;
				out.write(buffer, 0, nBytes);
			}
			out.flush();
		}
		finally
		{
			out.close();
			in.close();
		}

		return warFile.toString();
	}

	private boolean hasContext(final String sContext)
	{
		final HandlerCollection handlers = (HandlerCollection) _server.getChildHandlerByClass(HandlerCollection.class);
		final Handler[] contextHandlers = handlers.getChildHandlersByClass(org.eclipse.jetty.server.handler.ContextHandlerCollection.class);
        return hasContext(sContext, contextHandlers);
	}

	private boolean hasContext(final String sContext, final Handler[] contextHandlers)
	{
		if (contextHandlers == null)
			return false;

		for (Handler h : contextHandlers)
        {
        	if (h instanceof ContextHandler)
        	{
        		ContextHandler ch = (ContextHandler) h;
        		if (ch.getContextPath().equals(sContext))
        		{
        			return true;
        		}
        	}
        	else if (h instanceof HandlerCollection)
        	{
        		HandlerCollection hc = (HandlerCollection) h;
        		if (hasContext(sContext, hc.getHandlers()))
        			return true;
        	}
        }

		return false;
	}

	protected void configure() throws Exception
	{
		this.configuration = new Configuration();
		configuration.load();

		configSystemProperties();

		if (_instanceName != null)
		{
			configJDBC(_instanceName);
		}
		else
		{
			Log.warn("Parameter --instance is not found. Skip connecting to database.");
		}

		deployWebapps();
		deployBuiltInApplications();
	}

	protected void configSystemProperties()
	{
		if (configuration.getJavaProperties() == null)
		{
			Log.warn("File " + configuration.getJavaPropertiesFile() +
					" is not found. Skip loading system properties.");
			return;
		}

		Log.info("Loading system properties from " + configuration.getJavaPropertiesFile());

		if (Log.isDebugEnabled())
		{
			Log.debug("Loaded system properties: " + configuration.getJavaProperties());
		}

		//for (Object propName : configuration.getJavaProperties().keySet())
		//{
		//	String name = (String) propName;
		//}
		System.getProperties().putAll( configuration.getJavaProperties() );

		// Several hard-coded properties
		System.setProperty("process_name", "jetty-" + System.getProperty("user.name"));

		// R.Prasnakou 2009-12-17 Set RESOURCE_ROOT system property
		// if appropriate launching attribute is specified.
		if (_isResourceRootSystem)
		{
			System.setProperty("RESOURCE_ROOT", "http://" + host + ":" + port + "/");
		}
	}

	protected void configJDBC(String instanceName) throws Exception
	{
		// A.Solntsev 2009-02-04: Always set own context factory!
		if (true ||
				(System.getProperty("java.naming.factory.url.pkgs") == null)
				&& (System.getProperty("java.naming.factory.initial") == null))
		{
			System.setProperty("java.naming.factory.url.pkgs", "org.mortbay.jndi");
			System.setProperty("java.naming.factory.initial",
					"org.mortbay.naming.InitialContextFactory");

			Log.getLog().info("Runner.configJDBC: set java.naming.factory.url.pkgs=" + System.getProperty("java.naming.factory.url.pkgs"), null, null);
			Log.info("Runner.configJDBC: set java.naming.factory.initial=" + System.getProperty("java.naming.factory.initial"), null, null);
		}
		else
		{
			Log.getLog().info("Runner.configJDBC: java.naming.factory.url.pkgs=" + System.getProperty("java.naming.factory.url.pkgs"), null, null);
			Log.info("Runner.configJDBC: java.naming.factory.initial=" + System.getProperty("java.naming.factory.initial"), null, null);
		}

		// System.setProperty("java.naming.factory.url.pkgs", "org.mortbay.jndi");
		// System.setProperty("java.naming.factory.initial", "org.mortbay.naming.InitialContextFactory");

		Map<String, Properties> dataSources = configuration.getDataSources(instanceName);
		if (dataSources == null)
		{
			Log.warn("No data sources found for instance instanceName. Skip initializing JDBC.");
			return;
		}

		for (String jdbcJndiName : dataSources.keySet())
		{
			Properties p = dataSources.get(jdbcJndiName);

			if (!p.containsKey("driverClassName"))
			{
				// default value
				p.setProperty("driverClassName", "oracle.jdbc.OracleDriver");
			}

			String jdbcClass = p.getProperty("driverClassName");
            Class driver = Thread.currentThread().getContextClassLoader().loadClass(jdbcClass);
            Class nonXADataSourceBeanClass = Thread.currentThread().getContextClassLoader().loadClass("com.atomikos.jdbc.nonxa.NonXADataSourceBean");
            Log.info("Runner.configJDBC: driver="+driver+", nonXADataSourceBeanClass="+nonXADataSourceBeanClass.getName());

            Object o = nonXADataSourceBeanClass.newInstance();

            nonXADataSourceBeanClass.getMethod("setUniqueResourceName",
            		new Class[] {String.class}).invoke(o, new Object[] {jdbcJndiName});

            for (Object propName : p.keySet())
            {
            	String name = (String) propName;
            	// if (propName.equals("driverClassName"))
            	//	continue;

            	String methodName = "set" + name.substring(0,1).toUpperCase() + name.substring(1);
            	nonXADataSourceBeanClass.getMethod(methodName, new Class[] {String.class}).invoke(o, new Object[] {p.getProperty(name)});
            }

            org.eclipse.jetty.plus.jndi.Resource jdbcResource =
            	new org.eclipse.jetty.plus.jndi.Resource(jdbcJndiName, o);
		}
	}

	/**
	 * @deprecated Method configJDBC() sould be used.
	 */
	protected int configJDBC(String[] args,int i) throws Exception
	{
		// A.Solntsev 2009-02-04: Always set own context factory!
		if (true ||
				(System.getProperty("java.naming.factory.url.pkgs") == null)
				&& (System.getProperty("java.naming.factory.initial") == null))
		{
			System.setProperty("java.naming.factory.url.pkgs", "org.mortbay.jndi");
			System.setProperty("java.naming.factory.initial",
					"org.mortbay.naming.InitialContextFactory");

			Log.getLog().info("Runner.configJDBC: set java.naming.factory.url.pkgs=" + System.getProperty("java.naming.factory.url.pkgs"), null, null);
			Log.info("Runner.configJDBC: set java.naming.factory.initial=" + System.getProperty("java.naming.factory.initial"), null, null);
		}
		else
		{
			Log.getLog().info("Runner.configJDBC: java.naming.factory.url.pkgs=" + System.getProperty("java.naming.factory.url.pkgs"), null, null);
			Log.info("Runner.configJDBC: java.naming.factory.initial=" + System.getProperty("java.naming.factory.initial"), null, null);
		}

	    String jdbcClass=null;
	    String jdbcProperties=null;
	    String jdbcJndiName=null;

	    if (!_isTxServiceAvailable)
	    {
	        Log.warn("JDBC TX support not found on classpath");
	        i+=3;
	    }
	    else
	    {
	        jdbcClass=args[++i];
	        jdbcProperties=args[++i];
	        jdbcJndiName=args[++i];

	        //check for jdbc resources to register
	        if (jdbcClass!=null)
	        {
	            if (isXADataSource(jdbcClass))
	            {
	                Class driver = Thread.currentThread().getContextClassLoader().loadClass(jdbcClass);
	            	Class simpleDataSourceBeanClass = Thread.currentThread().getContextClassLoader().loadClass("com.atomikos.jdbc.SimpleDataSourceBean");

	            	Log.info("Runner.configJDBC: driver="+driver+", simpleDataSourceBeanClass="+simpleDataSourceBeanClass.getName());
	                Object o = simpleDataSourceBeanClass.newInstance();
	                simpleDataSourceBeanClass.getMethod("setXaDataSourceClassName", new Class[] {String.class}).invoke(o, new Object[] {jdbcClass});
	                simpleDataSourceBeanClass.getMethod("setXaDataSourceProperties", new Class[] {String.class}).invoke(o, new Object[] {jdbcProperties});
	                simpleDataSourceBeanClass.getMethod("setUniqueResourceName", new Class[] {String.class}).invoke(o, new Object[] {jdbcJndiName});
	                //TODO reinstate when Atomikos fixes Class.forName in SimpleDataSourceBean
	                //com.atomikos.jdbc.SimpleDataSourceBean xaDataSourceBean = new  com.atomikos.jdbc.SimpleDataSourceBean();
	                //xaDataSourceBean.setXaDataSource(xaDS);
	                //xaDataSourceBean.setXaDataSourceClassName(xaDS.getClass().getCanonicalName());
	                //xaDataSourceBean.setXaDataSourceProperties(jdbcProperties);
	                //xaDataSourceBean.setUniqueResourceName(jdbcJndiName);
	                org.eclipse.jetty.plus.jndi.Resource jdbcResource = new org.eclipse.jetty.plus.jndi.Resource(jdbcJndiName, o);

	            }
	            else
	            {
	                String[] props = jdbcProperties.split(";");
	                String user=null;
	                String password=null;
	                String url=null;

	                for (int j=0;props!=null && j<props.length;j++)
	                {
	                    String[] pair = props[j].split("=");
	                    if (pair!=null && pair[0].equalsIgnoreCase("user"))
	                        user=pair[1];
	                    else if (pair!=null && pair[0].equalsIgnoreCase("password"))
	                        password=pair[1];
	                    else if (pair!=null && pair[0].equalsIgnoreCase("url"))
	                        url=pair[1];

	                }

	                Class driver = Thread.currentThread().getContextClassLoader().loadClass(jdbcClass);
	                Class nonXADataSourceBeanClass = Thread.currentThread().getContextClassLoader().loadClass("com.atomikos.jdbc.nonxa.NonXADataSourceBean");

	                Log.info("Runner.configJDBC: driver="+driver+", nonXADataSourceBeanClass="+nonXADataSourceBeanClass.getName());
	                Object o = nonXADataSourceBeanClass.newInstance();
	                nonXADataSourceBeanClass.getMethod("setDriverClassName", new Class[] {String.class}).invoke(o, new Object[] {jdbcClass});
	                nonXADataSourceBeanClass.getMethod("setUniqueResourceName", new Class[] {String.class}).invoke(o, new Object[] {jdbcJndiName});
	                nonXADataSourceBeanClass.getMethod("setUrl", new Class[] {String.class}).invoke(o, new Object[] {url});
	                nonXADataSourceBeanClass.getMethod("setUser", new Class[] {String.class}).invoke(o, new Object[] {user});
	                nonXADataSourceBeanClass.getMethod("setPassword", new Class[] {String.class}).invoke(o, new Object[] {password});
	                //TODO reinstate when Atomikos fix Class.forName in SimpleDataSourceBean
	                //com.atomikos.jdbc.nonxa.NonXADataSourceBean nonXaDataSourceBean = new com.atomikos.jdbc.nonxa.NonXADataSourceBean();
	                //nonXaDataSourceBean.setDriverClassName(jdbcClass);
	                //nonXaDataSourceBean.setUniqueResourceName(jdbcJndiName);
	                //nonXaDataSourceBean.setUrl(url);
	                //nonXaDataSourceBean.setUser(user);
	                //nonXaDataSourceBean.setPassword(password);
	                org.eclipse.jetty.plus.jndi.Resource jdbcResource = new org.eclipse.jetty.plus.jndi.Resource(jdbcJndiName, o);
	            }
	        }
	    }

	    return i;
	}

	protected void deployWebapps() throws IOException
	{
		if (_webapps == null || _webapps.isEmpty())
		{
			Log.info("No parameter --webapp found. Skip deploying webapps.");
			return;
		}

		for (String webapp : _webapps)
		{
			String contextName = null;
			if (webapp.indexOf('@') > -1)
			{
				// Parameter of form "--webapp context@war"
				String[] temp = webapp.split("@", 2);
				contextName = "/" + temp[0];
				webapp = temp[1];
			}

			if (webapp.endsWith(".war"))
			{
				File war = new File(webapp);
				if (war.exists())
				{
					if (contextName == null)
					{
						if (war.getName().equals("ROOT") || war.getName().equals("ROOT.war"))
							contextName = "/";
						else
							contextName = "/" + Utils.getFileName(war.getName());
					}

					addWebapp(war.getAbsolutePath(), contextName);
					continue;
				}
				else
				{
					Log.warn("File not found: " + war + ", skip deploying webapp.");
				}
			}

			String[] cp = webapp.split("\\+");
			String webappRoot = cp[0]; // +File.separatorChar+"target"+File.separatorChar+"WebRoot";
			File webXml = new File(webappRoot, "WEB-INF" + File.separatorChar + "web.xml");
			File pomXml = new File(cp[0], "pom.xml");

			boolean webappProject = false;
			if (webXml.exists())
			{
				if (contextName == null)
				{
					try
					{
						String webxml = IOUtils.readText(webXml);
						contextName = Webapp.extractContextFromWebXml(webxml);
					}
					catch (IOException e)
					{
						Log.warn("Failed to parse file " + webXml + ", skip deploying webapp.", e);
						continue;
					}
				}
			}
			else if (pomXml.exists())
			{
				try
				{
					if (contextName == null)
					{
						String pom = IOUtils.readText(pomXml);
						contextName = Webapp.extractContextFromPom(pom);
					}

					webappRoot = webappRoot + File.separatorChar + "src"+File.separatorChar+"main"+File.separatorChar+"webapp";
						//webappRoot + File.separatorChar + "target"+File.separatorChar+"WebRoot";
					webXml = new File(webappRoot, "WEB-INF" + File.separatorChar + "web.xml");
					webappProject = true;
				}
				catch (IOException e)
				{
					Log.warn("Failed to parse file " + pomXml + ", skip deploying webapp.", e);
					continue;
				}
			}
			else
			{
				Log.warn("Files " + pomXml + " and " + webXml + " not found, skip deploying webapp.");
				continue;
			}

			WebAppContext context = addWebapp(webappRoot, contextName);

			ClassLoader parentCL = Thread.currentThread().getContextClassLoader();
			WebAppClassLoader cl = new WebAppClassLoader(parentCL, context);

			// Build classpath
			{
				cl.addClassPath(cp[0] + File.separatorChar+"target"+File.separatorChar+"WebRoot"
						+File.separatorChar+"WEB-INF"+File.separatorChar+"classes");

				for (int i=1; i<cp.length; i++)
				{
					//if (cp[i].endsWith("classes"))
					cl.addClassPath(cp[i]);
				}

				if (!webappProject)
				{
					//context.setClassLoader(cl);
				}
				else
				{
					// TODO Use Eclipse project dependencies
					File lib = //new File(webappRoot, "WEB-INF" + File.separatorChar + "lib");
						new File(cp[0] + File.separatorChar+"target"+File.separatorChar+"WebRoot"
								+File.separatorChar+"WEB-INF"+File.separatorChar+"lib");
					if (lib.exists() && lib.isDirectory())
					{
						//WebAppClassLoader cl = new WebAppClassLoader(parentCL, context);
						// cl.addJars(Resource.newResource(lib.getAbsolutePath()));

						String[] jars = lib.list();
						//List<URL> jarUrls = new ArrayList<URL>(jars.length);
						forEachJar:
						for (String jar : jars)
						{
							// Check that this jar is not yet added among projects (in array cp[])
							for (int i=1; i<cp.length; i++)
							{
								String projectName = Utils.getProjectName(cp[i]);
								if (Utils.isSameJar(projectName, jar))
									continue forEachJar;
							}
							cl.addClassPath(lib.getCanonicalPath() + File.separatorChar + jar);
							//jarUrls.add(new File(lib.getCanonicalPath() + File.separatorChar + jar).toURL());
						}

						//URL[] urls = jarUrls.toArray(new URL[]{});
						//ClassLoader jarsCL = new URLClassLoader(urls, cl);
						//context.setClassLoader(jarsCL);
						// parentCL = cl;
					}
				}

				context.setClassLoader(cl);
				/*
				String t1 = ClassUtils.getCodeSource("org.apache.xerces.jaxp.DocumentBuilderFactoryImpl", cl);
				String t2 = ClassUtils.getCodeSource("org.springframework.beans.factory.xml.XmlBeanDefinitionReader", cl);
				String t3 = ClassUtils.getCodeSource("org.springframework.beans.factory.xml.DefaultDocumentLoader", cl);
				*/
			}

			// _contexts.addHandler(context);
		}
	}


	public void runAndWait() throws Exception
	{
		_mBeanContainer.start();
		_server.start();
	    _server.join();
	}

	public void run() throws Exception
	{
		_mBeanContainer.start();
		_server.start();
	}

	public void stop() throws Exception
	{
		_server.stop();
		_mBeanContainer.stop();
	}

	protected void expandJars(Resource lib) throws IOException
	{
	    String[] list = lib.list();
	    if (list==null)
	        return;

	    for (String path : list)
	    {
	        if (".".equals(path) || "..".equals(path))
	            continue;

	        Resource item = lib.addPath(path);

	        if (item.isDirectory())
	            expandJars(item);
	        else
	        {
	            if (path.toLowerCase().endsWith(".jar") ||
	                path.toLowerCase().endsWith(".zip"))
	            {
	                URL url = item.getURL();
	                _classpath.add(url);
	            }
	        }
	    }
	}

	protected void initClassLoader()
	{
	    if (_classLoader==null && _classpath!=null && _classpath.size()>0)
	    {
	        ClassLoader context=Thread.currentThread().getContextClassLoader();

	        if (context==null)
	            _classLoader=new URLClassLoader(_classpath.toArray(new URL[_classpath.size()]));
	        else
	            _classLoader=new URLClassLoader(_classpath.toArray(new URL[_classpath.size()]),context);

	        Thread.currentThread().setContextClassLoader(_classLoader);
	    }
	}


	protected boolean isXADataSource (String classname)
	throws Exception
	{
	    Class clazz = Thread.currentThread().getContextClassLoader().loadClass(classname);
	    boolean isXA=false;
	    while (!isXA && clazz!=null)
	    {
	        Class[] interfaces = clazz.getInterfaces();
	        for (int i=0;interfaces!=null &&!isXA && i<interfaces.length; i++)
	        {
	            if (interfaces[i].getCanonicalName().equals("javax.sql.XADataSource"))
	                isXA=true;
	        }
	        clazz=clazz.getSuperclass();
	    }
	    Log.debug(isXA?"XA":"!XA");
	    return isXA;
	}

	private void processTransactionManagement() throws Exception
	{
	    // A.Solntsev: deleted this code
	}


	public static void main(String[] args)
	{
		if (Log.getLog() == null)
		{
			System.err.println("WARNING: Logger is null!");
		}

	    Runner runner = new Runner();

	    try
	    {
	        if (args.length>0&&args[0].equalsIgnoreCase("--help"))
	        {
	            runner.usage(null);
	        }
	        else if (args.length>0&&args[0].equalsIgnoreCase("--version"))
	        {
	            System.err.println("org.eclipse.jetty.Server: "+Server.getVersion());
	            System.exit(1);
	        }

	        runner.configure(args);
	        runner.runAndWait();

	    }
	    catch (Exception e)
	    {
	        e.printStackTrace();
	        runner.usage(null);
	    }
	}

}
